Skip to content

feat(scorecard): File level checks#2751

Open
djanickova wants to merge 23 commits intoredhat-developer:mainfrom
djanickova:scorecard-file-level-checks
Open

feat(scorecard): File level checks#2751
djanickova wants to merge 23 commits intoredhat-developer:mainfrom
djanickova:scorecard-file-level-checks

Conversation

@djanickova
Copy link
Copy Markdown
Member

@djanickova djanickova commented Apr 13, 2026

Hey, I just made a Pull Request!

This PR introduces a new type of scorecard: file level checks (RHDHPLAN-397). This adds ability for Scorecard to verify if a list of required files (one or more) is present (such as a license, CODEOWNERS, dockerfile, .gitignore, etc). The paths to these files are configurable.

Example of these scorecards on the entity page:
Screenshot 2026-04-15 at 13 15 23

Example of these scorecards on homepage:
Screenshot 2026-04-15 at 13 16 05

Example config:

scorecard:
  plugins:
    github:
      files_check:
        files:
          - license: 'LICENSE'
          - codeowners: 'CODEOWNERS'
        schedule:
          frequency: { minutes: 5 }
          timeout: { minutes: 10 }
          initialDelay: { seconds: 5 }

Both app and app-legacy contain example scorecards by default, so you can just test this out by running yarn start (or yarn start:legacy). You can also edit the files and their paths to try out different scenarios.

The contents of this PR have been reviewed in 2 separate previous PRs within my fork:
djanickova#1
djanickova#2

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

@github-actions
Copy link
Copy Markdown
Contributor

This pull request adds a new top-level directory under workspaces/. Please follow Submitting a Pull Request for a New Workspace in CONTRIBUTING.md.

Signed-off-by: Diana Janickova <djanicko@redhat.com>

test:  implement and update unit tests

Assisted-By: Cursor
Signed-off-by: Diana Janickova <djanicko@redhat.com>

chore: generate api reports

Signed-off-by: Diana Janickova <djanicko@redhat.com>

ref: use metricId instead of providerId

Signed-off-by: Diana Janickova <djanicko@redhat.com>

ref: Apply suggestions from code review

Co-authored-by: Ihor Mykhno <imykhno@redhat.com>

fix: run prettier

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: update expected string in test

Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>

feat: implement aggregated card and update test

Signed-off-by: Diana Janickova <djanicko@redhat.com>

feat: include aggregated card in App.tsx

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: pass logger as param

Signed-off-by: Diana Janickova <djanicko@redhat.com>

ref: remove default boolean tresholds, edit translation

Signed-off-by: Diana Janickova <djanicko@redhat.com>

chore: generate api reports

Signed-off-by: Diana Janickova <djanicko@redhat.com>

test: e2e test scenario for file checks

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: selectors in test

Signed-off-by: Diana Janickova <djanicko@redhat.com>

feat: include file check section in app-config.yaml

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: aggregated permission card title and desc

Signed-off-by: Diana Janickova <djanicko@redhat.com>

ref: move tresholds from scorecard-common

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: off center icon and treshold imports

Signed-off-by: Diana Janickova <djanicko@redhat.com>

feat: add new translations

Signed-off-by: Diana Janickova <djanicko@redhat.com>

ref: implement resolveMetricTranslation

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: resolve translations correctly

Signed-off-by: Diana Janickova <djanicko@redhat.com>

ref: use useMemo in EmptyStatePanel

Signed-off-by: Diana Janickova <djanicko@redhat.com>

fix: use translations in test

Signed-off-by: Diana Janickova <djanicko@redhat.com>
@djanickova djanickova force-pushed the scorecard-file-level-checks branch from dd295a2 to 6a60828 Compare April 13, 2026 09:07
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
@rhdh-gh-app
Copy link
Copy Markdown

rhdh-gh-app bot commented Apr 14, 2026

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
app-legacy workspaces/scorecard/packages/app-legacy none v0.0.0
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github workspaces/scorecard/plugins/scorecard-backend-module-github minor v2.5.1
@red-hat-developer-hub/backstage-plugin-scorecard-backend workspaces/scorecard/plugins/scorecard-backend minor v2.5.1
@red-hat-developer-hub/backstage-plugin-scorecard-node workspaces/scorecard/plugins/scorecard-node minor v2.5.1
@red-hat-developer-hub/backstage-plugin-scorecard workspaces/scorecard/plugins/scorecard minor v2.5.1

Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
@rhdh-qodo-merge
Copy link
Copy Markdown

Review Summary by Qodo

Add batch metric providers and GitHub file existence checks

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Implement batch metric providers supporting multiple metrics per provider
• Add GitHub file existence checks (readme, license, codeowners, etc.)
• Refactor MetricProvidersRegistry to handle batch and single providers
• Add translation support for parameterized metric titles/descriptions
• Update UI components to display boolean metrics correctly
Diagram
flowchart LR
  A["MetricProvider Interface"] -->|extends| B["Batch Provider Support"]
  B -->|getMetricIds/getMetrics| C["Multiple Metrics"]
  B -->|calculateMetrics| D["Batch Calculation"]
  E["GithubFilesProvider"] -->|implements| B
  E -->|checks| F["File Existence"]
  G["MetricProvidersRegistry"] -->|manages| C
  G -->|deduplicates| H["Provider Storage"]
  I["Translation Utils"] -->|resolves| J["Parameterized Labels"]
  K["Scorecard Component"] -->|displays| L["Boolean Metrics"]
Loading

Grey Divider

File Changes

1. workspaces/scorecard/plugins/scorecard-node/src/api/MetricProvider.ts ✨ Enhancement +24/-0

Add optional batch provider methods to interface

workspaces/scorecard/plugins/scorecard-node/src/api/MetricProvider.ts


2. workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.ts ✨ Enhancement +96/-56

Support batch providers with multiple metric IDs

workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.ts


3. workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.ts ✨ Enhancement +131/-0

Implement batch provider for file existence checks

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.ts


View more (45)
4. workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubConfig.ts ⚙️ Configuration changes +37/-0

Define default thresholds for file checks

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubConfig.ts


5. workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts ✨ Enhancement +52/-0

Add checkFilesExist method using GraphQL queries

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts


6. workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/types.ts ✨ Enhancement +15/-0

Add GithubFile and GithubFilesConfig types

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/types.ts


7. workspaces/scorecard/plugins/scorecard-backend-module-github/src/module.ts ✨ Enhancement +5/-0

Register GithubFilesProvider in backend module

workspaces/scorecard/plugins/scorecard-backend-module-github/src/module.ts


8. workspaces/scorecard/plugins/scorecard-backend-module-github/config.d.ts ⚙️ Configuration changes +8/-0

Add files_check configuration schema

workspaces/scorecard/plugins/scorecard-backend-module-github/config.d.ts


9. workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts ✨ Enhancement +94/-1

Handle batch provider metric calculations and storage

workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts


10. workspaces/scorecard/plugins/scorecard-backend/src/service/CatalogMetricService.ts ✨ Enhancement +6/-6

Use registry.getMetric for batch provider metric resolution

workspaces/scorecard/plugins/scorecard-backend/src/service/CatalogMetricService.ts


11. workspaces/scorecard/plugins/scorecard-backend/src/service/router.ts ✨ Enhancement +5/-4

Use registry.getMetric for correct metric metadata

workspaces/scorecard/plugins/scorecard-backend/src/service/router.ts


12. workspaces/scorecard/plugins/scorecard/src/utils/translationUtils.ts ✨ Enhancement +56/-0

Implement cascading translation lookup with parameters

workspaces/scorecard/plugins/scorecard/src/utils/translationUtils.ts


13. workspaces/scorecard/plugins/scorecard/src/utils/index.ts ✨ Enhancement +1/-0

Export resolveMetricTranslation utility function

workspaces/scorecard/plugins/scorecard/src/utils/index.ts


14. workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx ✨ Enhancement +15/-2

Adjust display logic for boolean metric types

workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx


15. workspaces/scorecard/plugins/scorecard/src/components/Scorecard/EntityScorecardContent.tsx ✨ Enhancement +16/-16

Use translation resolution for batch provider metrics

workspaces/scorecard/plugins/scorecard/src/components/Scorecard/EntityScorecardContent.tsx


16. workspaces/scorecard/plugins/scorecard/src/components/Scorecard/CustomLegend.tsx ✨ Enhancement +4/-2

Hide expression for boolean threshold rules

workspaces/scorecard/plugins/scorecard/src/components/Scorecard/CustomLegend.tsx


17. workspaces/scorecard/plugins/scorecard/src/hooks/useMetricDisplayLabels.tsx ✨ Enhancement +8/-15

Use cascading translation lookup for metric labels

workspaces/scorecard/plugins/scorecard/src/hooks/useMetricDisplayLabels.tsx


18. workspaces/scorecard/plugins/scorecard/src/translations/ref.ts 📝 Documentation +6/-0

Add file check and boolean threshold translations

workspaces/scorecard/plugins/scorecard/src/translations/ref.ts


19. workspaces/scorecard/plugins/scorecard/src/translations/ja.ts 📝 Documentation +5/-0

Add Japanese translations for file checks

workspaces/scorecard/plugins/scorecard/src/translations/ja.ts


20. workspaces/scorecard/plugins/scorecard/src/translations/it.ts 📝 Documentation +5/-0

Add Italian translations for file checks

workspaces/scorecard/plugins/scorecard/src/translations/it.ts


21. workspaces/scorecard/plugins/scorecard/src/translations/fr.ts 📝 Documentation +6/-0

Add French translations for file checks

workspaces/scorecard/plugins/scorecard/src/translations/fr.ts


22. workspaces/scorecard/plugins/scorecard/src/translations/de.ts 📝 Documentation +5/-0

Add German translations for file checks

workspaces/scorecard/plugins/scorecard/src/translations/de.ts


23. workspaces/scorecard/plugins/scorecard/src/translations/es.ts 📝 Documentation +6/-0

Add Spanish translations for file checks

workspaces/scorecard/plugins/scorecard/src/translations/es.ts


24. workspaces/scorecard/plugins/scorecard/src/alpha/extensions/homePageCards.tsx ✨ Enhancement +50/-0

Add homepage widgets for file check metrics

workspaces/scorecard/plugins/scorecard/src/alpha/extensions/homePageCards.tsx


25. workspaces/scorecard/plugins/scorecard/src/alpha/index.tsx ✨ Enhancement +4/-0

Export file check homepage card widgets

workspaces/scorecard/plugins/scorecard/src/alpha/index.tsx


26. workspaces/scorecard/packages/app-legacy/src/App.tsx ✨ Enhancement +72/-2

Add file check scorecard cards to homepage

workspaces/scorecard/packages/app-legacy/src/App.tsx


27. workspaces/scorecard/plugins/scorecard-backend/__fixtures__/mockProviders.ts 🧪 Tests +112/-10

Add MockBatchBooleanProvider for testing

workspaces/scorecard/plugins/scorecard-backend/fixtures/mockProviders.ts


28. workspaces/scorecard/plugins/scorecard-backend/__fixtures__/mockMetricProvidersRegistry.ts 🧪 Tests +6/-1

Update mock registry to support batch providers

workspaces/scorecard/plugins/scorecard-backend/fixtures/mockMetricProvidersRegistry.ts


29. workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.test.ts 🧪 Tests +206/-11

Add comprehensive batch provider tests

workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.test.ts


30. workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.test.ts 🧪 Tests +184/-3

Add batch provider metric pulling tests

workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.test.ts


31. workspaces/scorecard/plugins/scorecard-backend/src/service/CatalogMetricService.test.ts 🧪 Tests +90/-5

Add batch provider metric retrieval tests

workspaces/scorecard/plugins/scorecard-backend/src/service/CatalogMetricService.test.ts


32. workspaces/scorecard/plugins/scorecard-backend/src/service/router.test.ts 🧪 Tests +108/-1

Add batch provider routing tests

workspaces/scorecard/plugins/scorecard-backend/src/service/router.test.ts


33. workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.test.ts 🧪 Tests +265/-0

Add comprehensive GithubFilesProvider tests

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.test.ts


34. workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GitHubClient.test.ts 🧪 Tests +79/-0

Add checkFilesExist method tests

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GitHubClient.test.ts


35. workspaces/scorecard/plugins/scorecard/src/utils/__tests__/translationUtils.test.ts 🧪 Tests +180/-0

Add translation resolution utility tests

workspaces/scorecard/plugins/scorecard/src/utils/tests/translationUtils.test.ts


36. workspaces/scorecard/plugins/scorecard/src/utils/__tests__/statusUtils.test.tsx 🧪 Tests +59/-1

Add boolean threshold status tests

workspaces/scorecard/plugins/scorecard/src/utils/tests/statusUtils.test.tsx


37. workspaces/scorecard/plugins/scorecard/src/hooks/__tests__/useMetricDisplayLabels.test.tsx 🧪 Tests +59/-0

Add cascading translation lookup tests

workspaces/scorecard/plugins/scorecard/src/hooks/tests/useMetricDisplayLabels.test.tsx


38. workspaces/scorecard/plugins/scorecard/src/components/Scorecard/__tests__/Scorecard.test.tsx 🧪 Tests +1/-0

Update Scorecard component tests for metricType

workspaces/scorecard/plugins/scorecard/src/components/Scorecard/tests/Scorecard.test.tsx


39. workspaces/scorecard/plugins/scorecard/src/components/Scorecard/__tests__/EntityScorecardContent.test.tsx 🧪 Tests +4/-0

Mock translation resolution in tests

workspaces/scorecard/plugins/scorecard/src/components/Scorecard/tests/EntityScorecardContent.test.tsx


40. workspaces/scorecard/packages/app-legacy/e2e-tests/scorecard.test.ts 🧪 Tests +66/-0

Add e2e tests for file check metrics

workspaces/scorecard/packages/app-legacy/e2e-tests/scorecard.test.ts


41. workspaces/scorecard/packages/app-legacy/e2e-tests/utils/scorecardResponseUtils.ts 🧪 Tests +71/-0

Add mock response for file check metrics

workspaces/scorecard/packages/app-legacy/e2e-tests/utils/scorecardResponseUtils.ts


42. workspaces/scorecard/plugins/scorecard-backend-module-github/README.md 📝 Documentation +55/-0

Document GitHub file checks configuration

workspaces/scorecard/plugins/scorecard-backend-module-github/README.md


43. workspaces/scorecard/plugins/scorecard-backend/README.md 📝 Documentation +7/-6

Add file checks to provider documentation table

workspaces/scorecard/plugins/scorecard-backend/README.md


44. workspaces/scorecard/plugins/scorecard/report.api.md 📝 Documentation +4/-0

Update API report with new translations

workspaces/scorecard/plugins/scorecard/report.api.md


45. workspaces/scorecard/plugins/scorecard/report-alpha.api.md 📝 Documentation +4/-0

Update alpha API report with new translations

workspaces/scorecard/plugins/scorecard/report-alpha.api.md


46. workspaces/scorecard/plugins/scorecard-node/report.api.md 📝 Documentation +3/-0

Update node API report with batch methods

workspaces/scorecard/plugins/scorecard-node/report.api.md


47. workspaces/scorecard/app-config.yaml 📝 Documentation +8/-0

Add example file checks configuration

workspaces/scorecard/app-config.yaml


48. workspaces/scorecard/.changeset/big-yaks-say.md 📝 Documentation +8/-0

Document batch provider and file checks changes

workspaces/scorecard/.changeset/big-yaks-say.md


Grey Divider

Qodo Logo

@rhdh-qodo-merge
Copy link
Copy Markdown

rhdh-qodo-merge bot commented Apr 15, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. No LICENSE/CODEOWNERS example card📎 Requirement gap ⚙ Maintainability
Description
The PR adds installable example homepage cards/widgets for github.files_check.readme and
github.files_check.i18guide, but does not provide an installable example card for both LICENSE
and CODEOWNERS checks. This fails the requirement to ship a reference example covering LICENSE
and CODEOWNERS.
Code

workspaces/scorecard/packages/app-legacy/src/App.tsx[R220-279]

+  {
+    Component: ScorecardHomepageCard as ComponentType,
+    config: {
+      id: 'scorecard-github.files_check.readme',
+      title: 'Scorecard: README file exists',
+      cardLayout: {
+        width: {
+          minColumns: 3,
+          maxColumns: 12,
+          defaultColumns: 4,
+        },
+        height: {
+          minRows: 5,
+          maxRows: 12,
+          defaultRows: 6,
+        },
+      },
+      layouts: {
+        xl: { w: 4, h: 6 },
+        lg: { w: 4, h: 6 },
+        md: { w: 4, h: 6 },
+        sm: { w: 4, h: 6 },
+        xs: { w: 4, h: 6 },
+        xxs: { w: 4, h: 6 },
+      },
+      props: {
+        aggregationId: 'github.files_check.readme',
+      },
+    },
+  },
+  {
+    Component: ScorecardHomepageCard as ComponentType,
+    config: {
+      id: 'scorecard-github.files_check.i18guide',
+      title: 'Scorecard: i18n guide file exists',
+      cardLayout: {
+        width: {
+          minColumns: 3,
+          maxColumns: 12,
+          defaultColumns: 4,
+        },
+        height: {
+          minRows: 5,
+          maxRows: 12,
+          defaultRows: 6,
+        },
+      },
+      layouts: {
+        xl: { w: 4, h: 6, x: 4 },
+        lg: { w: 4, h: 6, x: 4 },
+        md: { w: 4, h: 6, x: 4 },
+        sm: { w: 4, h: 6, x: 4 },
+        xs: { w: 4, h: 6, x: 4 },
+        xxs: { w: 4, h: 6, x: 4 },
+      },
+      props: {
+        aggregationId: 'github.files_check.i18guide',
+      },
+    },
+  },
Evidence
Compliance ID 6 requires an installable example Scorecard card for both LICENSE and CODEOWNERS.
The added example homepage cards/widgets are for README and i18guide only, and no installable
example card/widget for github.files_check.license and github.files_check.codeowners is included
in the PR changes.

An installable example Scorecard card is available for LICENSE and CODEOWNERS checks
workspaces/scorecard/packages/app-legacy/src/App.tsx[220-279]
workspaces/scorecard/plugins/scorecard/src/alpha/extensions/homePageCards.tsx[50-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Compliance requires an installable example Scorecard card for both `LICENSE` and `CODEOWNERS`, but the PR only adds example cards/widgets for `README` and `i18guide`.

## Issue Context
The PR introduces `github.files_check.*` and adds example homepage cards/widgets. To satisfy the checklist, provide installable example card/widget configurations specifically for `github.files_check.license` and `github.files_check.codeowners`.

## Fix Focus Areas
- workspaces/scorecard/packages/app-legacy/src/App.tsx[220-279]
- workspaces/scorecard/plugins/scorecard/src/alpha/extensions/homePageCards.tsx[50-56]
- workspaces/scorecard/plugins/scorecard/src/alpha/extensions/homePageCards.tsx[145-186]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Batch disable not applied🐞 Bug ≡ Correctness
Description
PullMetricsByProviderTask skips disabled-metric checks for batch providers, so metrics disabled via
scorecard.disabledMetrics or scorecard.io/disabled-metrics are still calculated and written to the
DB. This breaks the existing disable mechanism for github.files_check.* and any future batch
providers.
Code

workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[R146-214]

+            // Handle batch providers
+            if (isBatchProvider && provider.calculateMetrics) {
+              const entityRef = stringifyEntityRef(entity);
+              const entityKind = normalizeField(entity.kind);
+              const entityNamespace = normalizeField(entity.metadata.namespace);
+              const entityOwner = normalizeOwnerRef(entity?.spec?.owner);
+
+              try {
+                const resultsMap = await provider.calculateMetrics(entity);
+
+                // Create a result for each metric ID
+                return metricIds.map(metricId => {
+                  const value = resultsMap.get(metricId)!;
+
+                  try {
+                    const thresholds = mergeEntityAndProviderThresholds(
+                      entity,
+                      provider,
+                    );
+
+                    const status =
+                      this.thresholdEvaluator.getFirstMatchingThreshold(
+                        value,
+                        metricType,
+                        thresholds,
+                      );
+
+                    return {
+                      catalog_entity_ref: entityRef,
+                      metric_id: metricId,
+                      value,
+                      timestamp: new Date(),
+                      status,
+                      entity_kind: entityKind,
+                      entity_namespace: entityNamespace,
+                      entity_owner: entityOwner,
+                    } as DbMetricValueCreate;
+                  } catch (error) {
+                    return {
+                      catalog_entity_ref: entityRef,
+                      metric_id: metricId,
+                      value,
+                      timestamp: new Date(),
+                      error_message:
+                        error instanceof Error ? error.message : String(error),
+                      entity_kind: entityKind,
+                      entity_namespace: entityNamespace,
+                      entity_owner: entityOwner,
+                    } as DbMetricValueCreate;
+                  }
+                });
+              } catch (error) {
+                // If batch calculation fails, create error records for all metrics
+                return metricIds.map(
+                  metricId =>
+                    ({
+                      catalog_entity_ref: entityRef,
+                      metric_id: metricId,
+                      value: undefined,
+                      timestamp: new Date(),
+                      error_message:
+                        error instanceof Error ? error.message : String(error),
+                      entity_kind: entityKind,
+                      entity_namespace: entityNamespace,
+                      entity_owner: entityOwner,
+                    } as DbMetricValueCreate),
+                );
+              }
+            }
Evidence
The batch-provider code path creates metric values for every metricId returned by getMetricIds()
without consulting the disable rules, while the single-metric path explicitly calls
isMetricIdDisabled. The disable helper compares the provided metricId against app-config and entity
annotations, so batch providers must apply it per metricId (not just per provider).

workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[127-214]
workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[219-228]
workspaces/scorecard/plugins/scorecard-backend/src/utils/metricUtils.ts[31-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Batch providers bypass the existing disabled-metric mechanism. As a result, metrics disabled in `scorecard.disabledMetrics` or via `scorecard.io/disabled-metrics` annotations are still computed and persisted.

### Issue Context
Single-metric providers call `isMetricIdDisabled(...)` before calculating/storing a metric; batch providers should apply the same logic *per metricId*.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[127-214]
- workspaces/scorecard/plugins/scorecard-backend/src/utils/metricUtils.ts[31-56]

### What to change
- In the batch-provider branch, filter `metricIds` per entity using `isMetricIdDisabled(this.config, metricId, entity, logger)`.
- Only create DB records for enabled metricIds.
- Ensure the behavior matches the single-provider branch (disabled metrics produce no record, same as returning `undefined`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Metric values logged at info🐞 Bug ◔ Observability
Description
PullMetricsByProviderTask logs a JSON dump of every metric value being stored at info level,
including entity refs and metric values/statuses. This can flood production logs and expose internal
scoring data on every scheduler run.
Code

workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[R283-297]

+        // Log summary of batch results for debugging, will remove before final PR
+        if (batchResults.length > 0) {
+          const summary = batchResults.map(r => ({
+            entity: r.catalog_entity_ref,
+            metric: r.metric_id,
+            value: r.value,
+            status: r.status,
+            ...(r.error_message && { error: r.error_message }),
+          }));
+          logger.info(
+            `Storing ${batchResults.length} metric values: ${JSON.stringify(
+              summary,
+            )}`,
+          );
+        }
Evidence
The task logs a full summarized payload for every processed batch right before database insertion,
and the log statement is unconditional when batchResults is non-empty (normal case).

workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[283-297]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`PullMetricsByProviderTask` logs per-entity metric values at `info` level, which is too verbose for normal operation and can leak internal data.

### Issue Context
This happens in the main scheduler loop and will run frequently in production.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts[283-297]

### What to change
- Remove the log block entirely, or downgrade to `logger.debug(...)`.
- If you still need visibility, log only aggregate counts (e.g., number of entities processed, number of metric rows written, number of errors), not full per-entity details.
- Avoid `JSON.stringify(summary)` for large batches.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (2)
4. Unescaped GraphQL file paths🐞 Bug ☼ Reliability
Description
GithubClient.checkFilesExist interpolates configured file paths directly into the GraphQL query
string, so paths containing quotes/newlines can break the query and fail metric collection. This
makes metric pulling fragile to configuration content.
Code

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[R99-105]

+    for (const [metricId, path] of files) {
+      const sanitizedAlias = this.sanitizeGraphQLAlias(metricId);
+
+      aliasToMetricId.set(sanitizedAlias, metricId);
+      fileChecksParts.push(
+        `${sanitizedAlias}: object(expression: "HEAD:${path}") { id }`,
+      );
Evidence
File paths originate from config as raw strings and are placed inside a quoted GraphQL argument via
string interpolation, without escaping. Any path with characters that terminate or corrupt the
string literal will cause the query to be invalid and the provider to fail.

workspaces/scorecard/plugins/scorecard-backend-module-github/config.d.ts[35-41]
workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.ts[117-127]
workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[89-116]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`checkFilesExist` builds a GraphQL query by interpolating `path` into `"HEAD:${path}"` without escaping. This can create invalid GraphQL queries for certain configuration values.

### Issue Context
Paths are operator-configured strings; code should treat them as untrusted input for query construction.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[89-116]
- workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.ts[117-127]

### What to change
- Safely serialize the expression argument, e.g. build `const expr = `HEAD:${path}`;` and embed via `object(expression: ${JSON.stringify(expr)})` (note: JSON.stringify includes surrounding quotes and escapes).
- Optionally validate `path` (reject `\n`, `\r`, `"`, leading `./` or `/`, etc.) to match documented constraints and prevent surprising behavior.
- Add a unit test covering a path with a quote/newline to ensure it no longer breaks the query.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. GraphQL alias collisions🐞 Bug ≡ Correctness
Description
sanitizeGraphQLAlias can map different metric IDs to the same GraphQL alias, causing one file check
to overwrite another and return incorrect results. This is silent because the alias-to-metric
mapping uses the sanitized alias as the unique key.
Code

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[R85-105]

+  private sanitizeGraphQLAlias(alias: string): string {
+    return alias.replaceAll(/\W/g, '_');
+  }
+
+  async checkFilesExist(
+    url: string,
+    repository: GithubRepository,
+    files: Map<string, string>,
+  ): Promise<Map<string, boolean>> {
+    const octokit = await this.getOctokitClient(url);
+
+    const aliasToMetricId = new Map<string, string>();
+    const fileChecksParts: string[] = [];
+
+    for (const [metricId, path] of files) {
+      const sanitizedAlias = this.sanitizeGraphQLAlias(metricId);
+
+      aliasToMetricId.set(sanitizedAlias, metricId);
+      fileChecksParts.push(
+        `${sanitizedAlias}: object(expression: "HEAD:${path}") { id }`,
+      );
Evidence
The code replaces all non-word characters with '_' and uses the result as a map key
(aliasToMetricId). If two metricIds sanitize to the same string, the later one overwrites the
earlier entry, and only one metric gets mapped back correctly.

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[81-106]
workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.ts[117-127]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Two distinct metric IDs can sanitize to the same GraphQL alias (e.g., differing only by '-' vs '_'), which causes alias collisions and incorrect metric-to-result mapping.

### Issue Context
GraphQL aliases must be unique within the query. Current code derives them from metric IDs via lossy sanitization.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[81-106]

### What to change
- Detect collisions while building aliases (e.g., keep a `Set` of used aliases) and append a stable suffix (counter or short hash) when a collision occurs.
 - Example: `github_files_check_read_me` and `github_files_check_read_me__2`.
- Keep the alias->metricId mapping for *every* metric.
- Add a unit test that configures two metric IDs that would currently collide and assert both results are present and correct.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

6. Empty files yields invalid query🐞 Bug ☼ Reliability
Description
GithubClient.checkFilesExist builds a repository selection set from configured files; when files is
empty it produces an empty selection set, which is invalid GraphQL and will throw at runtime. There
is already a unit test that calls this method with an empty map, so this edge case is expected to
work.
Code

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[R96-116]

+    const aliasToMetricId = new Map<string, string>();
+    const fileChecksParts: string[] = [];
+
+    for (const [metricId, path] of files) {
+      const sanitizedAlias = this.sanitizeGraphQLAlias(metricId);
+
+      aliasToMetricId.set(sanitizedAlias, metricId);
+      fileChecksParts.push(
+        `${sanitizedAlias}: object(expression: "HEAD:${path}") { id }`,
+      );
+    }
+
+    const fileChecks = fileChecksParts.join('\n');
+
+    const query = `
+    query checkFilesExist($owner: String!, $repo: String!) {
+      repository(owner: $owner, name: $repo) {
+        ${fileChecks}
+      }
+    }
+  `;
Evidence
When no files are provided, fileChecksParts stays empty and the query interpolates an empty string
into repository { ... }, which results in an empty selection set. The test suite explicitly calls
checkFilesExist with an empty Map and expects an empty result map, implying this scenario should be
supported without calling GraphQL.

workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[96-116]
workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GitHubClient.test.ts[150-162]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
When `files.size === 0`, `checkFilesExist` still constructs and executes a GraphQL query that has an empty `repository {}` selection, which is invalid GraphQL.

### Issue Context
A unit test already exercises the empty-map case and expects an empty result map.

### Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts[89-116]
- workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GitHubClient.test.ts[150-162]

### What to change
- Add an early return at the start of `checkFilesExist`:
 - `if (files.size === 0) return new Map();`
- Update the unit test to assert the GraphQL client is **not** called when files is empty (optional but strengthens regression coverage).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread workspaces/scorecard/packages/app-legacy/src/App.tsx
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Signed-off-by: Diana Janickova <djanicko@redhat.com>
@djanickova djanickova changed the title Scorecard file level checks feat(scorecard): File level checks Apr 16, 2026
Signed-off-by: Diana Janickova <djanicko@redhat.com>
Copy link
Copy Markdown
Member

@Eswaraiahsapram Eswaraiahsapram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @djanickova, tested locally, looks good to me 🎉

/lgtm

Image Image Image

@imykhno
Copy link
Copy Markdown
Contributor

imykhno commented Apr 17, 2026

Thank you for the PR. I’ve reviewed the code and it looks good. However, during manual testing in my local environment, I noticed that for the Missing status, the color and value columns displayed incorrectly.

Screen.Recording.2026-04-17.at.19.35.14.mov

Comment on lines +119 to +148
const octokit = await this.getOctokitClient(url);

const aliasToMetricId = this.buildUniqueAliases([...files.keys()]);
const fileChecksParts: string[] = [];

for (const [alias, metricId] of aliasToMetricId) {
const path = files.get(metricId);
if (!path) continue;
const expr = `HEAD:${path}`;
fileChecksParts.push(
`${alias}: object(expression: ${JSON.stringify(expr)}) { id }`,
);
}

const fileChecks = fileChecksParts.join('\n');

const query = `
query checkFilesExist($owner: String!, $repo: String!) {
repository(owner: $owner, name: $repo) {
${fileChecks}
}
}
`;

const response = await octokit<{
repository: Record<string, { id: string } | null>;
}>(query, {
owner: repository.owner,
repo: repository.repo,
});
Copy link
Copy Markdown
Member

@christoph-jerolimov christoph-jerolimov Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @djanickova, in general this PR is really nice and thanks for all the work you put into this already.

But there is one general (and sorry, its a bigger one) change I like to ask for. The current implementation is a GitHub only implementation but we can expect that customers want check their GitLab, BitBucket etc. repos as well.

Instead of extending the GitHub Scorecard module, I think you should implement a complate Filecheck Scorecard module. And this module should use the ScmIntegration and UrlReaderService instead of Octokit to check if a file exist or not.

I will join the scorecard meeting today for this, let us know if you believe this bigger change is still possible. Sorry that noone mentioned that before.

@christoph-jerolimov christoph-jerolimov dismissed their stale review April 20, 2026 13:38

Up to the plugins team

@openshift-ci openshift-ci bot removed the lgtm label Apr 20, 2026
@openshift-ci
Copy link
Copy Markdown

openshift-ci bot commented Apr 20, 2026

New changes are detected. LGTM label has been removed.

Signed-off-by: Diana Janickova <djanicko@redhat.com>
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants